import Canvas2d from '../kernel/Canvas2d';

interface TextureOptions {
    xRepeat: boolean;
    yRepeat: boolean;
}

export interface Texture {
    texture: WebGLTexture;
    width: number;
    height: number;
}

class TextureManager extends Canvas2d {
    private _gl: WebGLRenderingContext;
    private _textureMap: Map<string, Texture> = new Map();
    private _hasRequestTextureMap: Map<string, Promise<Texture>> = new Map();
    private _emptyTexture: WebGLTexture;

    constructor(gl: WebGLRenderingContext) {
        super();
        this._gl = gl;

        // deal chrome warn
        this._emptyTexture = <WebGLTexture>this._gl.createTexture();
        this._gl.bindTexture(this._gl.TEXTURE_2D, this._emptyTexture);
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);

        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
        const pixels = document.createElement('canvas');
        this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, this._gl.RGBA, this._gl.UNSIGNED_BYTE, pixels);
    }

    getTexture(url: string, options: TextureOptions) {
        const key = `${url}-${options.xRepeat}-${options.yRepeat}`;
        return this._textureMap.get(key);
    }

    getEmptyTexture() {
        return this._emptyTexture;
    }

    loadTexture(url: string, options: TextureOptions) {
        const key = `${url}-${options.xRepeat}-${options.yRepeat}`;
        let loadPromise = this._hasRequestTextureMap.get(key);
        if (!loadPromise) {
            loadPromise = this._loadTexture(url, options);
            this._hasRequestTextureMap.set(key, loadPromise);
            loadPromise.then(() => {
                this._hasRequestTextureMap.delete(key);
            });
        }

        return loadPromise;
    }

    private async _loadTexture(url: string, options: TextureOptions) {
        const key = `${url}-${options.xRepeat}-${options.yRepeat}`;
        const imgData = await this._loadImage(url, options);
        const texture = this._initTexture(imgData, options);
        const { width, height } = imgData;
        const tempTexture: Texture = { texture, width, height };
        this._textureMap.set(key, tempTexture);
        return tempTexture;
    }

    private _initTexture(pixels: TexImageSource, options: TextureOptions) {
        const texture = <WebGLTexture>this._gl.createTexture();
        this._gl.bindTexture(this._gl.TEXTURE_2D, texture);

        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);

        const xParam = options.xRepeat ? this._gl.REPEAT : this._gl.CLAMP_TO_EDGE;
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, xParam);
        const yParam = options.yRepeat ? this._gl.REPEAT : this._gl.CLAMP_TO_EDGE;
        this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, yParam);

        this._gl.texImage2D(this._gl.TEXTURE_2D, 0, this._gl.RGBA, this._gl.RGBA, this._gl.UNSIGNED_BYTE, pixels);

        return texture;
    }

    private _loadImage(url: string, options: TextureOptions) {
        return new Promise<TexImageSource>((resolve, reject) => {
            const img = new Image();
            img.crossOrigin = 'anonymous';
            img.src = url;
            img.onload = () => {
                let { width, height } = img;
                if (options.xRepeat || options.yRepeat) {
                    width = 2 ** Math.ceil(Math.log2(img.width));
                    height = 2 ** Math.ceil(Math.log2(img.height));
                    this._ctx.clearRect(0, 0, width, height);
                    this._ctx.drawImage(img, 0, 0, width, height);
                    const pixels = this._ctx.getImageData(0, 0, width, height);
                    resolve(pixels);
                } else {
                    resolve(img);
                }
            };

            img.onerror = (err: any) => {
                reject(err);
            };
        });
    }

    destroy() {
        this._textureMap.clear();
    }
}

export default TextureManager;
